﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using Jacobi.Seesmic.FeedReader.Utilities;
using Seesmic.Sdp.Extensibility;

namespace Jacobi.Seesmic.FeedReader.Model
{
    /// <summary>
    /// Manages one feed and its item history.
    /// </summary>
    internal sealed class FeedManager
    {
        private FeedItemHistory _history = new FeedItemHistory();
        private bool _initialized;

        /// <summary>
        /// Constructs a new instance based on the <paramref name="feedInfo"/> and <paramref name="feedSource"/>.
        /// </summary>
        /// <param name="dataProvider">Owner. Must not be null.</param>
        /// <param name="feedInfo">Must not be null.</param>
        /// <param name="feedSource">Must not be null.</param>
        public FeedManager(DataProvider dataProvider, FeedInfo feedInfo, FeedSource feedSource)
        {
            dataProvider.ThrowIfArgumentIsNull("dataProvider");
            feedInfo.ThrowIfArgumentIsNull("feedInfo");
            feedSource.ThrowIfArgumentIsNull("feedSource");

            DataProvider = dataProvider;
            FeedInfo = feedInfo;
            FeedSource = feedSource;

            FeedInfo.PropertyChanged += new PropertyChangedEventHandler(FeedInfoPropertyChanged);
        }

        /// <summary>
        /// Gets the <see cref="FeedSource"/> this instance was constructed with.
        /// </summary>
        public FeedSource FeedSource { get; private set; }

        /// <summary>
        /// Gets the <see cref="FeedInfo"/> this instance was constructed with.
        /// </summary>
        public FeedInfo FeedInfo { get; private set; }

        /// <summary>
        /// The log service from the host application.
        /// </summary>
        private ILogService LogService
        {
            get { return DataProvider.LogService; }
        }

        /// <summary>
        /// Gets the <see cref="DataProvider"/> that owns this instance.
        /// </summary>
        private DataProvider DataProvider { get; set; }

        /// <summary>
        /// Initializes and starts the initial download.
        /// </summary>
        public void Start()
        {
            Initialize();

            BeginLoadFeedSource();
        }

        /// <summary>
        /// Removes all items for this instance from the timeline.
        /// </summary>
        public void Stop()
        {
            RemoveTimelineItems();
        }

        /// <summary>
        /// Wires up the <see cref="FeedSource"/>.
        /// </summary>
        private void Initialize()
        {
            if (_initialized) return;

            // this handler is also triggered by the timer.
            FeedSource.LoadCompleted += (sender, e) =>
            {
                if (e.Feed == null)
                {
                    LogService.Warning("Feed could not load: " + FeedInfo.Url);

                    if (e.Error != null)
                    {
                        LogService.Error(e.Error);
                    }
                    else if (e.Cancelled)
                    {
                        LogService.Warning("Loading was Cancelled.");
                    }
                    else
                    {
                        LogService.Warning("Feed Type mismatch (Atom10/Rss20).");
                    }
                }
                else
                {
                    UpdateHistory(e.Feed);
                    RefreshDisplay();

                    LogService.Info(string.Format(CultureInfo.InvariantCulture,
                        "Feed Reader loaded {0} items for feed {1}.",
                        e.Feed.Items.Count(), FeedInfo.Url));
                }
            };

            _initialized = true;
        }

        /// <summary>
        /// Called to update the tracked feed items.
        /// </summary>
        /// <param name="feed">Must not be null.</param>
        private void UpdateHistory(TimelineFeed feed)
        {
            foreach (TimelineFeedItem item in feed.Items)
            {
                _history.Update(item);
            }
        }

        /// <summary>
        /// Recalculates what items should be displayed on the timeline.
        /// </summary>
        /// <remarks>New items are added and old items are removed.</remarks>
        private void RefreshDisplay()
        {
            int maxItems = (FeedInfo.MaxItemCount == 0) ? _history.AllFeedItems.Count : FeedInfo.MaxItemCount;

            var items = (from feedItem in _history.AllFeedItems.Sorted.Take(maxItems)
                         select feedItem).ToList();

            var added = (from feedItem in items
                         where !_history.CurrentFeedItems.Contains(feedItem.UniqueId)
                         select feedItem).ToList();

            var removed = (from feedItem in _history.CurrentFeedItems
                           where !items.Contains(feedItem)
                           select feedItem).ToList();

            foreach (var item in removed)
            {
                FeedTimeline.Current.Remove(item.UniqueId, DataProvider.Remove);
                _history.CurrentFeedItems.Remove(item.UniqueId);
            }

            TimelineItemBuilder builder = new TimelineItemBuilder(this);

            foreach (TimelineFeedItem item in added)
            {
                var containerItem = builder.CreateContainer(item);
                DataProvider.Add(containerItem);

                _history.CurrentFeedItems.Add(item);
            }
        }

        /// <summary>
        /// Event handler when properties change on the <see cref="FeedInfo"/>.
        /// </summary>
        /// <param name="sender"><see cref="FeedInfo"/>.</param>
        /// <param name="e">The property name that changed.</param>
        public void FeedInfoPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            bool recreate = (e.PropertyName == FeedInfo.UrlPropertyName);
            bool reload = (e.PropertyName == FeedInfo.FeedTypePropertyName);
            bool refresh = (e.PropertyName == FeedInfo.MaxItemCountPropertyName);
            bool enabledChanged = (e.PropertyName == FeedInfo.EnabledPropertyName);

            if (recreate)
            {
                FeedSource.FeedUrl = new Uri(FeedInfo.Url);
            }

            if ((reload || enabledChanged) && FeedInfo.Enabled)
            {
                BeginLoadFeedSource();
            }

            if (enabledChanged && !FeedInfo.Enabled)
            {
                RemoveTimelineItems();
            }

            if (refresh)
            {
                RefreshDisplay();
            }
        }

        /// <summary>
        /// Helper to remove all items from the timeline that belong to this instance.
        /// </summary>
        private void RemoveTimelineItems()
        {
            FeedTimeline.Current.RemoveItems(this, DataProvider.Remove);
        }

        /// <summary>
        /// Manages loading new feeds (when/if last load etc).
        /// </summary>
        public void BeginLoadFeedSource()
        {
            if (!FeedInfo.Enabled) return;

            bool loadingStarted = FeedSource.BeginLoad();

            if (loadingStarted)
            {
                _history.LastFeedUpdate = DateTimeOffset.Now;
            }
        }

        //---------------------------------------------------------------------

        /// <summary>
        /// Helper class that tracks historic feed item information.
        /// </summary>
        private class FeedItemHistory
        {
            /// <summary>
            /// All feed items published by the <see cref="FeedSource"/> over time.
            /// </summary>
            public TimelineFeedItemCollection AllFeedItems = new TimelineFeedItemCollection();
            /// <summary>
            /// All feed items that are displayed at this moment.
            /// </summary>
            public TimelineFeedItemCollection CurrentFeedItems = new TimelineFeedItemCollection();
            /// <summary>
            /// The newest item currently displayed.
            /// </summary>
            public DateTimeOffset NewestItemDateTime;
            /// <summary>
            /// Time of the last download.
            /// </summary>
            public DateTimeOffset LastFeedUpdate;

            /// <summary>
            /// Update the history information with the <paramref name="feedItem"/>.
            /// </summary>
            /// <param name="feedItem">Must not be null.</param>
            public void Update(TimelineFeedItem feedItem)
            {
                if (NewestItemDateTime > feedItem.PublishDate)
                {
                    NewestItemDateTime = feedItem.PublishDate;
                }

                if (!AllFeedItems.Contains(feedItem.UniqueId))
                {
                    AllFeedItems.Add(feedItem);
                }
            }
        }
    }
}